package org.offsync.util

import java.util.concurrent.TimeoutException

/**
 * An instance in time relative to specific instance in time relative to some fixed but arbitrary time.
 * The time is not related to any other notion of system or wall-clock time and is useful for calculating
 * elapsed time.
 * @author John
 */
sealed abstract class Time extends Ordered[Time] {
  /**
   * The number of nanoseconds past the fixed but arbitrary time used as the reference point.
   * @return
   */
  def nanoseconds: Long
  def microseconds = nanoseconds / 1000
  def milliseconds = microseconds / 1000
  def seconds = milliseconds / 1000
  
  def -(that: Time): Duration
  
  def timeLeft: Duration
  
  def expired: Boolean
  
  def schedule(): ScheduledTime = new ScheduledTime(this)
  
  def schedule(task: => Unit): ScheduledTask = ScheduledTask({() => task}, this.schedule())
  
  def schedule(task: Option[Throwable] => Unit): ScheduledTask = ScheduledTask(task, this.schedule())
  
  def enforceDeadline(): Unit = {
    if (Time.now < this) {
      throw new TimeoutException("Deadline expired")
    }
  }
}

object Never extends Time {
  override def nanoseconds: Long = Long.MaxValue

  override def -(that: Time): Duration = Forever
  
  override def timeLeft: Duration = Forever
  
  override def expired: Boolean = false
  
  override def toString(): String = "Never"

  def compare(that: Time): Int = if (that == Never) 0 else 1
}

case class TimeNanos(val nanoseconds: Long) extends Time {  
  override def -(that: Time): Duration = DurationNanos(nanoseconds - that.nanoseconds)

  override def timeLeft: Duration = this - Time.now
  
  override def expired: Boolean = nanoseconds <= System.nanoTime

  def compare(that: Time): Int = {
    if (that == Never) return -1
    
    if (this.nanoseconds < that.nanoseconds) return -1
    if (this.nanoseconds > that.nanoseconds) return 1
    
    return 0
  }
}

object Time {
  def atNanos(nanoseconds: Long): Time = {
    if (nanoseconds == Long.MaxValue) Never else TimeNanos(nanoseconds)
  }

  val minValue = atNanos(Long.MinValue)

  val zero = TimeNanos(0)
  
  def now = TimeNanos(System.nanoTime)
}
